package com.tianji.learning.service.impl;

import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.po.PointsBoard;
import com.tianji.learning.domain.po.PointsBoardSeason;
import com.tianji.learning.domain.query.PointsBoardQuery;
import com.tianji.learning.domain.vo.PointsBoardItemVO;
import com.tianji.learning.domain.vo.PointsBoardVO;
import com.tianji.learning.mapper.PointsBoardMapper;
import com.tianji.learning.service.IPointsBoardSeasonService;
import com.tianji.learning.service.IPointsBoardService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.utils.TableInfoContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundZSetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.cache.annotation.Cacheable;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.tianji.learning.constants.TableConstants.POINTS_BOARD_TABLE_PREFIX;
import static com.tianji.learning.constants.TableConstants.POINTS_RECORD_TABLE_PREFIX;

/**
 * <p>
 * 学霸天梯榜 服务实现类
 * </p>
 *
 * @author huanmo
 * @since 2025-09-24
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements IPointsBoardService {

    private final StringRedisTemplate redisTemplate;

    private final UserClient userClient;

    private final IPointsBoardSeasonService seasonService;

    private final PointsBoardAsyncService pointsBoardAsyncService;

    private final IPointsBoardSeasonService boardSeasonService;

    @Override
    public PointsBoardVO queryPointsBoardBySeason(PointsBoardQuery query) {
        LocalDateTime now = LocalDateTime.now();
        // 获取当前赛季
        Integer currentSeason = boardSeasonService.querySeasonByTime(now);
        // 处理查询的赛季参数
        Long season = query.getSeason();
        // 如果查询的赛季为空或0，默认为当前赛季
        boolean isCurrent = season == null || season == 0 || season.equals(currentSeason.longValue());
        // 如果不是查询当前赛季，检查赛季有效性
        if (!isCurrent) {
            // 赛季不能大于当前赛季
            if (season > currentSeason) {
                log.warn("赛季{}不存在", season);
                return null;
            }
        }
        // 1.获取Redis的Key
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + now.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);
        // 2.查询我的积分和排名
        PointsBoard myBoard = isCurrent ?
                // 查询当前榜单（Redis）
                queryMyCurrentBoard(key) :
                // 查询历史榜单（MySQL）
                queryMyHistoryBoard(season);
        // 3.查询榜单列表
        List<PointsBoard> list = isCurrent ?
                queryCurrentBoardList(key, query.getPageNo(), query.getPageSize()) :
                queryHistoryBoardList(query);
        // 4.封装VO
        PointsBoardVO vo = new PointsBoardVO();
        // 4.1.处理我的信息
        if (myBoard != null) {
            vo.setPoints(myBoard.getPoints());
            vo.setRank(myBoard.getRank());
        }
        if (CollUtils.isEmpty(list)) {
            return vo;
        }
        // 4.2.查询用户信息
        Set<Long> uIds = list.stream().map(PointsBoard::getUserId).collect(Collectors.toSet());
        List<UserDTO> users = userClient.queryUserByIds(uIds);
        Map<Long, String> userMap = new HashMap<>(uIds.size());
        if (CollUtils.isNotEmpty(users)) {
            userMap = users.stream().collect(Collectors.toMap(UserDTO::getId, UserDTO::getName));
        }
        // 4.3.处理榜单列表
        List<PointsBoardItemVO> items = new ArrayList<>(list.size());
        for (PointsBoard p : list) {
            PointsBoardItemVO v = new PointsBoardItemVO();
            v.setPoints(p.getPoints());
            v.setRank(p.getRank());
            v.setName(userMap.get(p.getUserId()));
            items.add(v);
        }
        vo.setBoardList(items);
        return vo;
    }

    @Override
    public void createPointsBoardTableBySeason(Integer season) {
        baseMapper.createPointsBoardTable(POINTS_BOARD_TABLE_PREFIX + season);
    }

    @Override
    public void createPointsRecordTableBySeason(Integer season) {
        baseMapper.createPointsRecordTable(POINTS_RECORD_TABLE_PREFIX + season);
    }

    private List<PointsBoard> queryHistoryBoardList(PointsBoardQuery query) {
        // 获取缓存key
        String key = getKey(Math.toIntExact(query.getSeason()));
        // 查询缓存
        List<PointsBoard> redisBoards = queryCurrentBoardList(key, query.getPageNo(), query.getPageSize());
        if (CollUtils.isNotEmpty(redisBoards)) {
            log.info("已从Redis中获取第{}赛季的排行榜数据", query.getSeason());
            return redisBoards;
        }
        // 限制最大查询100条
        int pageSize = Math.min(query.getPageSize(), 100);
        int pageNo = query.getPageNo();
        // 查询总数
        String tableName = POINTS_BOARD_TABLE_PREFIX + query.getSeason();
        Long total = baseMapper.selectCountByTableName(tableName);
        if (total == 0) {
            return List.of();
        }
        // 计算分页参数
        Long offset = (long) (pageNo - 1) * pageSize;
        // 查询数据
        List<PointsBoard> records = baseMapper.selectByTableName(tableName, offset, pageSize);
        if (CollUtils.isEmpty(records)) {
            return List.of();
        }
        // 异步写入Redis（不阻塞主流程）
        pointsBoardAsyncService.asyncWriteToRedis(key, records, Math.toIntExact(query.getSeason()));
        log.info("已将第{}赛季的排行榜数据异步写入Redis", query.getSeason());
        // 返回数据
        return records.stream()
                .filter(r -> r.getUserId() != null && r.getPoints() != null)
                .map(r -> PointsBoard.builder()
                        .userId(r.getUserId())
                        .points(r.getPoints())
                        .rank(Math.toIntExact(r.getId()))
                        .build())
                .collect(Collectors.toList());
    }

    @Override
    public List<PointsBoard> queryCurrentBoardList(String key, Integer pageNo, Integer pageSize) {
        // 1.计算分页
        // 限制最大查询100条
        int validPageSize = Math.min(pageSize != null ? pageSize : 10, 100);
        int from = (pageNo - 1) * validPageSize;
        // 2.查询
        Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet()
                .reverseRangeWithScores(key, from, from + validPageSize - 1);
        if (CollUtils.isEmpty(tuples)) {
            return CollUtils.emptyList();
        }
        // 3.封装
        int rank = from + 1;
        List<PointsBoard> list = new ArrayList<>(tuples.size());
        for (ZSetOperations.TypedTuple<String> tuple : tuples) {
            String userId = tuple.getValue();
            Double points = tuple.getScore();
            if (userId == null || points == null) {
                continue;
            }
            PointsBoard p = PointsBoard.builder()
                    .userId(Long.valueOf(userId))
                    .points(points.intValue())
                    .rank(rank++).build();
            list.add(p);
        }
        return list;
    }

    private PointsBoard queryMyHistoryBoard(Long season) {
        String userId = UserContext.getUser().toString();
        // 1.先查询缓存
        // 获取缓存Key
        String key = getKey(Math.toIntExact(season));
        PointsBoard pointsBoard = queryUserRank(key, Long.valueOf(userId));
        if (pointsBoard != null) {
            log.info("已从Redis中获取用户{}第{}赛季的排行榜数据", userId, season);
            return pointsBoard;
        }
        // 2.再查询数据库
        String tableName = POINTS_BOARD_TABLE_PREFIX + season;
        try {
            // 设置动态表名到上下文
            TableInfoContext.setInfo(tableName);
            PointsBoard p = lambdaQuery()
                    .select(PointsBoard::getId,
                            PointsBoard::getUserId,
                            PointsBoard::getPoints)
                    .eq(PointsBoard::getUserId, userId)
                    .one();
            return PointsBoard.builder()
                    .userId(p.getUserId())
                    .points(p.getPoints())
                    .rank(Math.toIntExact(p.getId()))
                    .build();
        } finally {
            // 清理上下文，避免内存泄漏
            TableInfoContext.remove();
        }
    }

    /**
     * 查询指定用户在排行榜中的积分和排名
     */
    private PointsBoard queryUserRank(String key, Long userId) {
        try {
            // 1. 查询用户的积分（分数）
            Double score = redisTemplate.opsForZSet().score(key, String.valueOf(userId));
            if (score == null) {
                return null;
            }
            // 2. 查询用户的排名（从0开始，按分数从大到小排序）
            Long rank = redisTemplate.opsForZSet().reverseRank(key, String.valueOf(userId));
            // 3. 查询总人数
            return PointsBoard.builder()
                    .userId(userId)
                    .points(score.intValue())
                    .rank(rank != null ? rank.intValue() + 1 : null)
                    .build();
        } catch (Exception e) {
            return null;
        }
    }

    private PointsBoard queryMyCurrentBoard(String key) {
        // 1.绑定key
        BoundZSetOperations<String, String> ops = redisTemplate.boundZSetOps(key);
        // 2.获取当前用户信息
        String userId = UserContext.getUser().toString();
        // 3.查询积分
        Double points = ops.score(userId);
        // 4.查询排名
        Long rank = ops.reverseRank(userId);
        // 5.封装返回
        PointsBoard p = new PointsBoard();
        p.setPoints(points == null ? 0 : points.intValue());
        p.setRank(rank == null ? 0 : rank.intValue() + 1);
        return p;
    }

    public String getKey(Integer season) {
        // 生成缓存key
        String cacheKey = "points:season:key:" + season;
        // 先尝试从缓存获取
        String cachedKey = redisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.isNotBlank(cachedKey)) {
            return cachedKey;
        }
        // 缓存不存在，查询数据库
        PointsBoardSeason pointsBoardSeason = seasonService.queryBySeason(Math.toIntExact(season));
        LocalDateTime begin = pointsBoardSeason.getBeginTime().atStartOfDay();
        String key = RedisConstants.HISTORY_POINTS_BOARD_PREFIX + begin.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);
        // 写入缓存（缓存24小时）
        redisTemplate.opsForValue().set(cacheKey, key, Duration.ofHours(24));
        return key;
    }
}
